-- Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
-- 
-- this file licensed under the Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN \"AS IS\" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
-- PURPOSE.
-- See the Mulan PSL v2 for more details.
--

local class = require 'mc.class'
local skynet = require 'skynet'
local std_smbus = require 'protocol_open.protocol.std_smbus'
local log = require 'mc.logging'

local get_busy_command_code<const> = 0x22
local IDLE_STATUS<const> = 0x10
local check_busy_count<const> = 100
local max_retry_count <const> = 3

local request_params_template <const> = {
    opcode = true,
    offset = true,
    expect_data_len = true,
    arg = true,
    data = true,
    max_frame_len = true,
    write_without_read = true,
    align_len = true
}

local smbus_5902 = class(std_smbus)

function smbus_5902:check_busy()
    for _ = 1, check_busy_count do
        local ok, data = self.super.receive(self, get_busy_command_code, 1)
        if ok then
            local stat = data:byte()
            if stat == IDLE_STATUS then
                return true
            end
        end
        skynet.sleep(self:get_delay_time())
    end
    return false
end

function smbus_5902:send_and_receive(data, len)
    local ok = self:check_busy()
    if not ok then
        return false, 'chip busy, unable to send command'
    end
    ok = smbus_5902.super.send(self, 0, data)
    if not ok then
        return false, 'unable to send command'
    end

    local ok = self:check_busy()
    if not ok then
        return false, 'chip busy, unable to retrieve response'
    end
    return self.super.receive(self, 0, len)
end

function smbus_5902:send(data)
    local ok = self:check_busy()
    if not ok then
        return false, 'chip busy, unable to send command'
    end
    ok = smbus_5902.super.send(self, 0, data)
    if not ok then
        return false, 'unable to send command'
    end

    return true
end

function smbus_5902:send_request_in_frames(track_request)
    -- 最后一帧只发剩余长度，其他帧发最大长度
    self:calc_last_frame_len(track_request)

    local req_bin = self:construct_request_data(track_request)
    if req_bin == '' then
        return false, 'unable to construct request data'
    end
    return self:send(req_bin)
end

function smbus_5902:write(track_request)
    skynet.sleep(self:get_delay_time())
    local ok, msg = self:send_request_in_frames(track_request)
    if not ok then
        return ok, msg
    end
    
    return true
end

function smbus_5902:write_without_read(track_request)
    local retry_count = 0
    local is_write_completed = false
    repeat
        local ok, rsp_raw = self:write(track_request)
        if ok then
            -- 重置重试counter
            retry_count = 0
            track_request.data_written = track_request.data_written + track_request.len
            is_write_completed = track_request.data_written >= #track_request.data
        else
            if retry_count < max_retry_count then
                retry_count = retry_count + 1
                log:debug('failed to write request protocol: %s, msg: %s. Retry: %d/%d',
                    self.name, rsp_raw, retry_count, max_retry_count)
            else
                log:debug(
                    'failed to write request protocol: %s, msg: %s. Reach max retry, exit',
                    self.name, rsp_raw)
                return nil
            end
        end
    until is_write_completed
end

function smbus_5902:unpack_response_data(rsp_bin, _)
    local result = self.response_bs:unpack(rsp_bin, true)
    if not result then
        return false, 'unable to parse smbus 5902 response binary'
    end
    return true, result.body
end

function smbus_5902:ctor()
    self.name = 'smbus_5902'
    self.request_params_template = request_params_template
end

return smbus_5902
